//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package user_api_serv

import (
	"context"
	"strings"
	"time"

	"atomgit.com/openlinksaas/api-server/api_common"
	"atomgit.com/openlinksaas/api-server/config"
	"atomgit.com/openlinksaas/api-server/dao"
	"atomgit.com/openlinksaas/api-server/dao/org_dao"
	"atomgit.com/openlinksaas/api-server/dao/project_dao"
	"atomgit.com/openlinksaas/api-server/dao/user_dao"
	"atomgit.com/openlinksaas/proto-gen-go.git/notices/notices_org"
	"atomgit.com/openlinksaas/proto-gen-go.git/notices/notices_project"
	"atomgit.com/openlinksaas/proto-gen-go.git/user_api"
	"github.com/dchest/uniuri"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/mongo"
)

type UserApiImpl struct {
	user_api.UnimplementedUserApiServer

	servCfg *config.ServerConfig
}

func NewUserApiImpl(servCfg *config.ServerConfig) *UserApiImpl {
	go watchUser()
	return &UserApiImpl{
		servCfg: servCfg,
	}
}

func (apiImpl *UserApiImpl) Login(ctx context.Context, req *user_api.LoginRequest) (*user_api.LoginResponse, error) {
	if req.UserType == user_api.USER_TYPE_USER_TYPE_INTERNAL {
		return apiImpl.loginInternal(ctx, req)
	} else if req.UserType == user_api.USER_TYPE_USER_TYPE_ATOM_GIT {
		return apiImpl.loginAtomGit(ctx, req)
	} else if req.UserType == user_api.USER_TYPE_USER_TYPE_GITEE {
		return apiImpl.loginGitee(ctx, req)
	} else if req.UserType == user_api.USER_TYPE_USER_TYPE_JIHU_LAB {
		return apiImpl.loginJihulab(ctx, req)
	} else if req.UserType == user_api.USER_TYPE_USER_TYPE_GIT_CODE {
		return apiImpl.loginGitCode(ctx, req)
	}
	return &user_api.LoginResponse{
		Code:   user_api.LoginResponse_CODE_WRONG_USERNAME,
		ErrMsg: "用户不存在",
	}, nil
}

func (apiImpl *UserApiImpl) loginInternal(ctx context.Context, req *user_api.LoginRequest) (*user_api.LoginResponse, error) {
	userInfoItem, err := user_dao.UserInfoDao.GetByName(ctx, req.UserName)
	if err != nil {
		return &user_api.LoginResponse{
			Code:   user_api.LoginResponse_CODE_WRONG_USERNAME,
			ErrMsg: "用户不存在",
			UserInfo: &user_api.UserInfo{
				BasicInfo: &user_api.BasicUserInfo{},
			},
		}, nil
	}
	err = user_dao.UserPasswdDao.Auth(ctx, userInfoItem.UserId, req.Passwd)
	if err != nil {
		return &user_api.LoginResponse{
			Code:   user_api.LoginResponse_CODE_WRONG_PASSWD,
			ErrMsg: "密码错误",
			UserInfo: &user_api.UserInfo{
				BasicInfo: &user_api.BasicUserInfo{},
			},
		}, nil
	}
	if userInfoItem.UserState == uint32(user_api.USER_STATE_USER_STATE_FORBIDDEN) {
		return &user_api.LoginResponse{
			Code:   user_api.LoginResponse_CODE_USER_FORBIDDEN,
			ErrMsg: "用户被冻结",
			UserInfo: &user_api.UserInfo{
				BasicInfo: &user_api.BasicUserInfo{},
			},
		}, nil
	}

	secretItem, err := user_dao.UserSecretDao.Get(ctx, userInfoItem.UserId)
	if err != nil {
		secretItem = &user_dao.UserSecretDbItem{}
	}
	//创建会话
	sessionId := uniuri.NewLen(32) + strings.ReplaceAll(uuid.NewString(), "-", "")

	dao.CacheDao.SetSession(ctx, &dao.SessionDbItem{
		SessionId:   sessionId,
		UserId:      userInfoItem.UserId,
		UserName:    userInfoItem.UserName,
		DisplayName: userInfoItem.BasicInfo.DisplayName,
		LogoUri:     userInfoItem.BasicInfo.LogoUri,
		UserType:    uint32(user_api.USER_TYPE_USER_TYPE_INTERNAL),
		TestAccount: userInfoItem.TestAccount,
	})

	go sendOnlineNotice(userInfoItem.UserId)

	return &user_api.LoginResponse{
		Code:       user_api.LoginResponse_CODE_OK,
		SessionId:  sessionId,
		UserInfo:   userInfoItem.ToUserInfo(),
		UserSecret: secretItem.Secret,
		ExtraToken: "",
	}, nil
}

func (apiImpl *UserApiImpl) Logout(ctx context.Context, req *user_api.LogoutRequest) (*user_api.LogoutResponse, error) {
	dao.CacheDao.RemoveSession(ctx, req.SessionId)

	return &user_api.LogoutResponse{
		Code: user_api.LogoutResponse_CODE_OK,
	}, nil
}

func (apiImpl *UserApiImpl) Update(ctx context.Context, req *user_api.UpdateRequest) (*user_api.UpdateResponse, error) {
	sessItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &user_api.UpdateResponse{
			Code:   user_api.UpdateResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err := user_dao.UserInfoDao.UpdateBasicInfo(sessCtx, sessItem.UserId, &user_dao.BasicUserInfoDbItem{
			DisplayName: req.BasicInfo.DisplayName,
			LogoUri:     req.BasicInfo.LogoUri,
		})
		if err != nil {
			return nil, err
		}
		return &user_api.UpdateResponse{
			Code: user_api.UpdateResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	dao.CacheDao.SetSession(ctx, &dao.SessionDbItem{
		SessionId:   sessItem.SessionId,
		UserId:      sessItem.UserId,
		UserName:    sessItem.UserName,
		DisplayName: req.BasicInfo.DisplayName,
		LogoUri:     req.BasicInfo.LogoUri,
		UserType:    uint32(user_api.USER_TYPE_USER_TYPE_INTERNAL),
		TestAccount: sessItem.TestAccount,
	})
	//发送更新信息通知
	projectIdList, err := project_dao.MemberInfoDao.ListProjectByMember(ctx, sessItem.UserId)
	if err == nil {
		for _, projectId := range projectIdList {
			go api_common.SendNoticeToProjectScope(context.Background(), projectId, &notices_project.UpdateMemberNotice{
				ProjectId:    projectId,
				MemberUserId: sessItem.UserId,
			})
		}
	}
	orgIdList, err := org_dao.MemberDao.DistinctByUser(ctx, sessItem.UserId)
	if err == nil {
		for _, orgId := range orgIdList {
			go api_common.SendNoticeToOrgScope(context.Background(), orgId, &notices_org.UpdateMemberNotice{
				OrgId:        orgId,
				MemberUserId: sessItem.UserId,
			})
		}
	}
	return ret.(*user_api.UpdateResponse), nil
}

func (apiImpl *UserApiImpl) UpdateFeature(ctx context.Context, req *user_api.UpdateFeatureRequest) (*user_api.UpdateFeatureResponse, error) {
	sessItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &user_api.UpdateFeatureResponse{
			Code:   user_api.UpdateFeatureResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)
	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = user_dao.UserInfoDao.UpdateFeatureInfo(sessCtx, sessItem.UserId, &user_dao.FeatureInfoDbItem{
			EnableProject: req.Feature.EnableProject,
			EnableOrg:     req.Feature.EnableOrg,
		})
		if err != nil {
			return nil, err
		}
		return &user_api.UpdateFeatureResponse{
			Code: user_api.UpdateFeatureResponse_CODE_OK,
		}, nil
	})

	if err != nil {
		return nil, err
	}
	return ret.(*user_api.UpdateFeatureResponse), nil
}

func (apiImpl *UserApiImpl) ChangePasswd(ctx context.Context, req *user_api.ChangePasswdRequest) (*user_api.ChangePasswdResponse, error) {
	sessItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &user_api.ChangePasswdResponse{
			Code:   user_api.ChangePasswdResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}

	if sessItem.TestAccount {
		return &user_api.ChangePasswdResponse{
			Code:   user_api.ChangePasswdResponse_CODE_WRONG_OLD_PASSWD,
			ErrMsg: "体验账号无法修改密码",
		}, nil
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return nil, err
	}
	defer dao.EndSession(dbSess)

	ret, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
		err := user_dao.UserPasswdDao.ChangePasswd(sessCtx, sessItem.UserId, req.OldPasswd, req.NewPasswd)
		if err != nil {
			return &user_api.ChangePasswdResponse{
				Code:   user_api.ChangePasswdResponse_CODE_WRONG_OLD_PASSWD,
				ErrMsg: "原密码错误",
			}, nil
		}
		return &user_api.ChangePasswdResponse{
			Code: user_api.ChangePasswdResponse_CODE_OK,
		}, nil
	})
	if err != nil {
		return nil, err
	}
	return ret.(*user_api.ChangePasswdResponse), nil
}

func (apiImpl *UserApiImpl) KeepAlive(ctx context.Context, req *user_api.KeepAliveRequest) (*user_api.KeepAliveResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &user_api.KeepAliveResponse{
			Code:   user_api.KeepAliveResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	newExtraToken := ""

	if sessionItem.UserType == uint32(user_api.USER_TYPE_USER_TYPE_ATOM_GIT) && (time.Now().Unix()-sessionItem.ExtraLastRefresh) > 7200 { //atomgit用户token两个小时未刷新
		accessToken, refreshToken, err := refreshAtomGitAccessToken(apiImpl.servCfg.AtomGit.ClientId, apiImpl.servCfg.AtomGit.ClientSecret, sessionItem.ExtraRefreshToken)
		if err != nil {
			return nil, err
		}
		sessionItem.ExtraAccessToken = accessToken
		sessionItem.ExtraRefreshToken = refreshToken
		sessionItem.ExtraLastRefresh = time.Now().Unix()
		dao.CacheDao.SetSession(ctx, sessionItem) //skip error check

		newExtraToken = accessToken
	} else if sessionItem.UserType == uint32(user_api.USER_TYPE_USER_TYPE_GITEE) && (time.Now().Unix()-sessionItem.ExtraLastRefresh) > 7200 { //gitee用户token两个小时未刷新
		accessToken, refreshToken, err := refreshGiteeAccessToken(sessionItem.ExtraRefreshToken)
		if err != nil {
			return nil, err
		}
		sessionItem.ExtraAccessToken = accessToken
		sessionItem.ExtraRefreshToken = refreshToken
		sessionItem.ExtraLastRefresh = time.Now().Unix()
		dao.CacheDao.SetSession(ctx, sessionItem) //skip error check

		newExtraToken = accessToken
	} else if sessionItem.UserType == uint32(user_api.USER_TYPE_USER_TYPE_JIHU_LAB) && (time.Now().Unix()-sessionItem.ExtraLastRefresh) > 1800 { //jihulab用户token半个小时未刷新
		accessToken, refreshToken, err := refreshJihulabAccessToken(apiImpl.servCfg.Jihulab.ClientId, apiImpl.servCfg.Jihulab.ClientSecret, sessionItem.ExtraRefreshToken)
		if err != nil {
			return nil, err
		}
		sessionItem.ExtraAccessToken = accessToken
		sessionItem.ExtraRefreshToken = refreshToken
		sessionItem.ExtraLastRefresh = time.Now().Unix()
		dao.CacheDao.SetSession(ctx, sessionItem) //skip error check

		newExtraToken = accessToken
	} else if sessionItem.UserType == uint32(user_api.USER_TYPE_USER_TYPE_GIT_CODE) && (time.Now().Unix()-sessionItem.ExtraLastRefresh) > 7200 { //gitcode用户token两个小时未刷新
		accessToken, refreshToken, err := refreshGitCodeAccessToken(sessionItem.ExtraRefreshToken)
		if err != nil {
			return nil, err
		}
		sessionItem.ExtraAccessToken = accessToken
		sessionItem.ExtraRefreshToken = refreshToken
		sessionItem.ExtraLastRefresh = time.Now().Unix()
		dao.CacheDao.SetSession(ctx, sessionItem) //skip error check

		newExtraToken = accessToken
	}
	dao.CacheDao.KeepSessionAlive(ctx, req.SessionId)
	return &user_api.KeepAliveResponse{
		Code:          user_api.KeepAliveResponse_CODE_OK,
		NewExtraToken: newExtraToken,
	}, nil
}

func (apiImpl *UserApiImpl) CheckSession(ctx context.Context, req *user_api.CheckSessionRequest) (*user_api.CheckSessionResponse, error) {
	_, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	return &user_api.CheckSessionResponse{
		Code:  user_api.CheckSessionResponse_CODE_OK,
		Valid: ok,
	}, nil
}

func (apiImpl *UserApiImpl) GenOneTimeToken(ctx context.Context, req *user_api.GenOneTimeTokenRequest) (*user_api.GenOneTimeTokenResponse, error) {
	sessionItem, ok := dao.CacheDao.GetSession(ctx, req.SessionId)
	if !ok {
		return &user_api.GenOneTimeTokenResponse{
			Code:   user_api.GenOneTimeTokenResponse_CODE_WRONG_SESSION,
			ErrMsg: "会话不存在",
		}, nil
	}
	token := uniuri.NewLenChars(128, []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))
	err := dao.CacheDao.SetUserOnetimeToken(ctx, token, sessionItem.UserId)
	if err != nil {
		return nil, err
	}
	return &user_api.GenOneTimeTokenResponse{
		Code:  user_api.GenOneTimeTokenResponse_CODE_OK,
		Token: token,
	}, nil
}
